#include "RenderSystem.h" 
#include "Paras.h"
#include "Camera.h"
#include "ITexture.h"
#include "Light.h"
#include "Material.h"
#include "VertexBuffer.h"
#include "IndexBuffer.h"
//
namespace Euclid
{
	RenderSystem::RenderSystem()
		: _d3d9(0), _device(0), _3DMode(false)
	{
		 memset(_vertexDeclaration, 0, sizeof(_vertexDeclaration));
	}

	RenderSystem::~RenderSystem()
	{
		destroy();
	}

	bool RenderSystem::create()
	{
		//
		_d3d9 = Direct3DCreate9(D3D_SDK_VERSION);
		if (NULL == _d3d9)
		{
			return false;
		}

		//                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
		unsigned int	adapter			= D3DADAPTER_DEFAULT;

		/*
		HAL (Hardware Abstraction Layer) devices and REF (REFerence rasterizer) devices are the two main types of D3D device;
		the first is based around the hardware support, and is very fast but might not support everything; 
		while the second uses no hardware acceleration, so is very slow, but is guaranteed to support the entire set of D3D features, 
		in the correct way. In general you'll only ever need to use HAL devices, but if you're using some advanced feature that your 
		graphics card does not support then you might need to fall back to REF.
		*/
		D3DDEVTYPE		deviceType		= D3DDEVTYPE_HAL;

		char tmp[512];
		//
		D3DADAPTER_IDENTIFIER9 ai;
		if (!FAILED(_d3d9->GetAdapterIdentifier(adapter,0,&ai)))
		{
			unsigned short product = HIWORD(ai.DriverVersion.HighPart);
			unsigned short version = LOWORD(ai.DriverVersion.HighPart);
			unsigned short subVersion = HIWORD(ai.DriverVersion.LowPart);
			unsigned short build = LOWORD(ai.DriverVersion.LowPart);

			sprintf_s(tmp,"%s %s %d.%d.%d.%d\n",ai.Description,ai.Driver,product,version,subVersion,build);
		}

		//
		_d3d9->GetDeviceCaps(adapter, deviceType, &_caps);
		DWORD	vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;
		/*if (_caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT)
		{
			vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;
		}
		else
		{
			vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
		}*/

		//
		_initPresentParas();

		////
		//if(FAILED(_d3d9->CheckDeviceType(adapter, deviceType, _presentParas.BackBufferFormat,
		//	_presentParas.BackBufferFormat, FALSE)))
		//{
		//	_presentParas.BackBufferFormat = D3DFMT_R5G6B5;

		//	if(FAILED(_d3d9->CheckDeviceType(adapter, deviceType, _presentParas.BackBufferFormat,
		//		_presentParas.BackBufferFormat, FALSE)))
		//	{
		//		// Use the current display mode.
		//		D3DDISPLAYMODE mode;

		//		if(FAILED(_d3d9->GetAdapterDisplayMode(adapter, &mode)))
		//		{
		//			return false;
		//		}

		//		_presentParas.BackBufferFormat = mode.Format;

		//		if(FAILED(_d3d9->CheckDeviceType(adapter, deviceType, _presentParas.BackBufferFormat,
		//			_presentParas.BackBufferFormat, FALSE)))
		//		{
		//			return false;
		//		}
		//	}
		//}

		//
		HRESULT hr;
		//	try creating a Hardware T&L device first
		hr = _d3d9->CreateDevice(adapter, deviceType, Paras::getSingletonPtr()->_hwnd,
			vp, &_presentParas, &_device);
		if (FAILED(hr))
		{
			//	try Mixed
			vp = D3DCREATE_MIXED_VERTEXPROCESSING;
			hr = _d3d9->CreateDevice(adapter, deviceType, Paras::getSingletonPtr()->_hwnd,
				vp, &_presentParas, &_device);

			if (FAILED(hr))
			{
				//	try Software
				vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
				hr = _d3d9->CreateDevice(adapter, deviceType, Paras::getSingletonPtr()->_hwnd,
					vp, &_presentParas, &_device);

				if (FAILED(hr))
				{
					//	give up :(
					return false;
				}
			}
		}

		//
		_initVertexDeclaration();

		//
		_camera = new Camera;
		Paras::getSingletonPtr()->_camera = _camera;
		
		//
		Paras::getSingletonPtr()->_device = _device;

		//
		switchProjectionMode();

		//
		return true;
	}

	void RenderSystem::_initPresentParas()
	{
		//
		RECT rect;
		GetClientRect(_hwnd, &rect);

		ZeroMemory(&_presentParas, sizeof(_presentParas));
		// 
		_presentParas.BackBufferCount			= 1;
		_presentParas.BackBufferFormat			= D3DFMT_X8R8G8B8;
		_presentParas.BackBufferWidth			= rect.right - rect.left;
		_presentParas.BackBufferHeight			= rect.bottom - rect.top;
		//
		_presentParas.MultiSampleType			= D3DMULTISAMPLE_4_SAMPLES;
		_presentParas.MultiSampleQuality		= 4;
		//
		_presentParas.EnableAutoDepthStencil	= true;
		_presentParas.AutoDepthStencilFormat	= D3DFMT_D24S8;
		//
		_presentParas.Windowed					= true;
		//
		_presentParas.SwapEffect				= D3DSWAPEFFECT_DISCARD;
		// 
		_presentParas.hDeviceWindow				= Paras::getSingletonPtr()->_hwnd;
		//
		_presentParas.Flags						= 0;
		//
		_presentParas.FullScreen_RefreshRateInHz= D3DPRESENT_RATE_DEFAULT;
		//
		_presentParas.PresentationInterval		= D3DPRESENT_INTERVAL_IMMEDIATE;
	}

	void RenderSystem::destroy()
	{
		if (_device)
		{
			_device->Release();
		}

		//
		if (_d3d9)
		{
			_d3d9->Release();
		}
	}

	void RenderSystem::_initVertexDeclaration()
	{
		//
		D3DVERTEXELEMENT9 ve[VD_COUNT][4] = 
		{
			//	VD_NULL
			{
				D3DDECL_END()
			},

			//	VD_POSITION_COLOR
			{
				{0,		0,	D3DDECLTYPE_FLOAT3,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION,	0},
				{0,		12,	D3DDECLTYPE_D3DCOLOR,	D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR,		0},
				D3DDECL_END()
			},

			//	VD_TPOSITION_COLOR
			{
				{0,		0,	D3DDECLTYPE_FLOAT4,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITIONT,	0},
				{0,		16,	D3DDECLTYPE_D3DCOLOR,	D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR,		0},
				D3DDECL_END()
			},

			//	VD_POSITION_NORMAL
			{
				{0,		0,	D3DDECLTYPE_FLOAT3,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION,	0},
				{0,		12,	D3DDECLTYPE_FLOAT3,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,		0},
				D3DDECL_END()
			},

			//	VD_POSITION_TEXTURE
			{
				{0,		0,	D3DDECLTYPE_FLOAT3,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION,	0},
				{0,		12,	D3DDECLTYPE_FLOAT2,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD,	0},
				D3DDECL_END()
			},

			//	VD_TPOSITION_TEXTURE
			{
				{0,		0,	D3DDECLTYPE_FLOAT4,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITIONT,	0},
				{0,		16,	D3DDECLTYPE_FLOAT2,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD,	0},
				D3DDECL_END()
			},

			//	VD_POSITION_COLOR_TEXTURE
			{
				{ 0,	0,	D3DDECLTYPE_FLOAT3,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION,	0},
				{ 0,	12, D3DDECLTYPE_D3DCOLOR,	D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR,		0},
				{ 0,	16, D3DDECLTYPE_FLOAT2,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD,	0},
				D3DDECL_END()
			},

			//	VD_TPOSITION_COLOR_TEXTURE
			{
				{ 0,	0,	D3DDECLTYPE_FLOAT4,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITIONT,	0},
				{ 0,	16, D3DDECLTYPE_D3DCOLOR,	D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR,		0},
				{ 0,	20, D3DDECLTYPE_FLOAT2,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD,	0},
				D3DDECL_END()
			},

			//	VD_POSITION_NORMAL_TEXTURE
			{
				{ 0,	0,	D3DDECLTYPE_FLOAT3,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION,	0},
				{ 0,	12, D3DDECLTYPE_FLOAT3,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,		0},
				{ 0,	24, D3DDECLTYPE_FLOAT2,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD,	0},
				D3DDECL_END()
			},
		};
		
		//
		for (unsigned char i = VD_NULL; i != VD_COUNT; ++i)
		{
			_device->CreateVertexDeclaration(ve[i], _vertexDeclaration + i);
		}
	}

	void RenderSystem::beginFrame( bool clearColorBuffer /*= true*/, bool clearDepthBuffer /*= true*/, bool clearStencilBuffer /*= false*/ )
	{
		if (_device == NULL)
		{
			return;
		}
		DWORD flag = 0;
		if (clearColorBuffer)
		{
			flag |= D3DCLEAR_TARGET;
		}
		if (clearDepthBuffer)
		{
			flag |= D3DCLEAR_ZBUFFER;
		}
		if (clearStencilBuffer)
		{
			flag |= D3DCLEAR_STENCIL;
		}
		_device->Clear(0, 0, flag, D3DCOLOR_ARGB(0, 0, 0, 0), 1.0f, 0L);
		_device->BeginScene();
	}

	void RenderSystem::endFrame()
	{
		_device->EndScene();
		HRESULT hr = _device->Present(0, 0, 0, 0);
		if (hr == D3DERR_DEVICELOST)
		{
			resetDevice();
		}
	}

	bool RenderSystem::queryShaderCaps( unsigned int major, unsigned int minor )
	{
		//
		if (_caps.VertexShaderVersion < D3DVS_VERSION(major, minor) ||
			_caps.PixelShaderVersion < D3DPS_VERSION(major, minor))
		{
			return false;
		}

		//
		return true;
	}

	void RenderSystem::drawPoint( D3DXVECTOR3 point, D3DCOLOR color )
	{
		POSITION_COLOR v[] =
		{
			POSITION_COLOR(point, color),
		};

		setVertexDeclaration(VD_POSITION_COLOR);
		_device->DrawPrimitiveUP(D3DPT_POINTLIST,1, (const void*)v, sizeof(POSITION_COLOR));
		setVertexDeclaration(VD_NULL);
	}

	void RenderSystem::drawLine(D3DXVECTOR3 beginPoint, D3DXVECTOR3 endPoint, D3DCOLOR color)
	{
		POSITION_COLOR v[] =
		{
			POSITION_COLOR(beginPoint, color),
			POSITION_COLOR(endPoint, color)
		};
		setVertexDeclaration(VD_POSITION_COLOR);
		_device->DrawPrimitiveUP(D3DPT_LINELIST,1, (const void*)v, sizeof(POSITION_COLOR));
		setVertexDeclaration(VD_NULL);
	}

	void RenderSystem::setVertexDeclaration( eVertexDeclarationType vd )
	{
		_device->SetVertexDeclaration(_vertexDeclaration[vd]);
	}

	bool RenderSystem::setViewport( const D3DVIEWPORT9 * pViewport )
	{
		if (NULL == _device)
		{
			return false;
		}

		//
		HRESULT hr = _device->SetViewport(pViewport);
		if (FAILED(hr))
		{
			return false;
		}

		//
		return true;
	}


	bool RenderSystem::setWorldMatrix( const D3DMATRIX * pMatrix )
	{
		if (NULL == _device)
		{
			return false;
		}

		//
		HRESULT hr = _device->SetTransform(D3DTS_WORLD, pMatrix);
		if (FAILED(hr))
		{
			return false;
		}

		//
		return true;
	}

	bool RenderSystem::setViewMatrix( const D3DMATRIX * pMatrix )
	{
		if (NULL == _device)
		{
			return false;
		}

		//
		//
		HRESULT hr = _device->SetTransform(D3DTS_VIEW, pMatrix);
		if (FAILED(hr))
		{
			return false;
		}

		//
		return true;
	}

	bool RenderSystem::setProjectionMatrix( const D3DMATRIX * pMatrix )
	{
		if (NULL == _device)
		{
			return false;
		}

		//
		//
		HRESULT hr = _device->SetTransform(D3DTS_PROJECTION, pMatrix);
		if (FAILED(hr))
		{
			return false;
		}

		//
		return true;
	}

	bool RenderSystem::update()
	{
		if (NULL == _camera)
		{
			return false;
		}

		//
		_camera->update();

		//
		D3DXMATRIX m;
		if (!setViewMatrix(_camera->getViewMatrix(m)))
		{
			return false;
		}

		//
		return true;
	}

	bool RenderSystem::setTexture( unsigned short index, ITexture* tex )
	{
		if (NULL == _device)
		{
			return false;
		}

		//
		if (NULL == tex)
		{
			return false;
		}

		//
		HRESULT hr = _device->SetTexture(index, tex->getTexture());
		if (FAILED(hr))
		{
			return false;
		}

		//
		return true;
	}

	bool RenderSystem::drawPrimitive( D3DPRIMITIVETYPE PrimitiveType, unsigned int StartVertex, unsigned int PrimitiveCount )
	{
		if (NULL == _device)
		{
			return false;
		}

		//
		HRESULT hr = _device->DrawPrimitive(PrimitiveType, StartVertex, PrimitiveCount);
		if (FAILED(hr))
		{
			return false;
		}

		//
		return true;
	}

	bool RenderSystem::drawPrimitiveUP( D3DPRIMITIVETYPE PrimitiveType, unsigned int PrimitiveCount, const void* pVertexStreamZeroData, unsigned int VertexStreamZeroStride )
	{
		if (NULL == _device)
		{
			return false;
		}

		//
		HRESULT hr = _device->DrawPrimitiveUP(PrimitiveType, PrimitiveCount, pVertexStreamZeroData, VertexStreamZeroStride);
		if (FAILED(hr))
		{
			return false;
		}

		//
		return true;
	}

	bool RenderSystem::drawIndexedPrimitive( D3DPRIMITIVETYPE Type, int BaseVertexIndex, unsigned int MinIndex, unsigned int NumVertices, unsigned int StartIndex, unsigned int PrimitiveCount )
	{
		if (NULL == _device)
		{
			return false;
		}

		//
		HRESULT hr = _device->DrawIndexedPrimitive(Type, BaseVertexIndex, MinIndex, NumVertices, StartIndex, PrimitiveCount);
		if (FAILED(hr))
		{
			return false;
		}

		//
		return true;
	}

	bool RenderSystem::drawIndexedPrimitiveUP( D3DPRIMITIVETYPE PrimitiveType, unsigned int MinVertexIndex, unsigned int NumVertices, unsigned int PrimitiveCount, const void * pIndexData, D3DFORMAT IndexDataFormat, const void* pVertexStreamZeroData, unsigned int VertexStreamZeroStride )
	{
		if (NULL == _device)
		{
			return false;
		}

		//
		HRESULT hr = _device->DrawIndexedPrimitiveUP(PrimitiveType, MinVertexIndex, NumVertices, PrimitiveCount, pIndexData, IndexDataFormat, pVertexStreamZeroData, VertexStreamZeroStride);
		if (FAILED(hr))
		{
			return false;
		}

		//
		return true;
	}

	bool RenderSystem::getWorldMatrix( D3DMATRIX * pMatrix )
	{
		if (NULL == _device)
		{
			return false;
		}

		//
		HRESULT hr = _device->GetTransform(D3DTS_WORLD, pMatrix);
		if (FAILED(hr))
		{
			return false;
		}

		//
		return true;
	}

	bool RenderSystem::setTextureStageState( unsigned long Stage, D3DTEXTURESTAGESTATETYPE Type, unsigned long Value )
	{
		if (NULL == _device)
		{
			return false;
		}

		//
		HRESULT hr = _device->SetTextureStageState( Stage, Type, Value );
		if (FAILED(hr))
		{
			return false;
		}

		//
		return true;
	}

	bool RenderSystem::setRenderState( D3DRENDERSTATETYPE State, unsigned long Value )
	{
		if (NULL == _device)
		{
			return false;
		}

		//
		HRESULT hr = _device->SetRenderState( State, Value );
		if (FAILED(hr))
		{
			return false;
		}

		//
		return true;
	}

	bool RenderSystem::setSamplerState( unsigned long Sampler, D3DSAMPLERSTATETYPE Type, unsigned long Value )
	{
		if (NULL == _device)
		{
			return false;
		}

		//
		HRESULT hr = _device->SetSamplerState( Sampler, Type, Value );
		if (FAILED(hr))
		{
			return false;
		}

		//
		return true;
	}

	bool RenderSystem::setLight( unsigned int index, Light* light )
	{
		if (NULL == _device)
		{
			return false;
		}

		if (NULL == light)
		{
			_device->LightEnable(index, false);
			return true;
		}

		//
		D3DLIGHT9* lig = NULL;
		lig = light->getLight();
		if (NULL == lig)
		{
			return false;
		}

		//
		HRESULT hr = _device->SetLight( index, lig);
		if (FAILED(hr))
		{
			return false;
		}

		//
		hr = _device->LightEnable( index, true);
		if (FAILED(hr))
		{
			return false;
		}

		//
		return true;
	}

	bool RenderSystem::setMaterial( Material* material )
	{
		if (NULL == _device)
		{
			return false;
		}

		//
		HRESULT hr = _device->SetMaterial(material->getMaterial());
		if (FAILED(hr))
		{
			return false;
		}

		//
		return true;
	}

	bool RenderSystem::setStreamSource( unsigned int StreamNumber, VertexBuffer* pStreamData, unsigned int OffsetInBytes, unsigned int Stride )
	{
		if (NULL == _device)
		{
			return false;
		}

		//
		HRESULT hr = _device->SetStreamSource( StreamNumber, pStreamData->getVertexBuffer(), OffsetInBytes, Stride );
		if (FAILED(hr))
		{
			return false;
		}

		//
		return true;
	}

	bool RenderSystem::setIndices( IndexBuffer* pIndexData )
	{
		if (NULL == _device)
		{
			return false;
		}

		//
		HRESULT hr = _device->SetIndices(pIndexData->getIndexBuffer());
		if (FAILED(hr))
		{
			return false;
		}

		//
		return true;
	}

	void RenderSystem::switchProjectionMode()
	{
		D3DXMATRIX vm;

		_3DMode = !_3DMode;
		//
		if (_3DMode)
		{
			//
			D3DXMatrixPerspectiveFovLH(&vm, D3DX_PI * 0.25, 1.0f, 0.001f, 100000.f);
		}
		else
		{
			//
			D3DXMatrixOrthoLH(&vm, _presentParas.BackBufferWidth, _presentParas.BackBufferHeight, 1.0f, 1000.0f);
		}

		//
		setProjectionMatrix(&vm);
	}

	bool RenderSystem::isPerspectiveProjectionMode()
	{
		return _3DMode;
	}

	bool RenderSystem::isOrthographicProjectionMode()
	{
		return !_3DMode;
	}

	void RenderSystem::resetCamera()
	{
		if (_camera)
		{
			_camera->reset();
		}
	}

	void RenderSystem::resetDevice()
	{

	}

	void RenderSystem::drawQuad( POSITION_TEXTURE* v, ITexture* tex )
	{
		//
		static unsigned short i[6] = {0, 1, 2, 0, 2, 3};
		
		setVertexDeclaration(VD_POSITION_TEXTURE);
		
		setTexture(0, tex);

		//
		drawIndexedPrimitiveUP(D3DPT_TRIANGLELIST, 0, 4, 2, i, 
			D3DFMT_INDEX16, v, sizeof(POSITION_TEXTURE));

		//
		setTexture(0, 0);
	}
}